#include "head/applicationdata.h"
#include "head/global.h"

#include <QFile>
#include <QDir>
#include <QDebug>
#include <QRegularExpression>

void ApplicationData::createConfigExample(const QString &pathToConfig)
{
    QFile file(pathToConfig);
    if (not file.open(QIODevice::WriteOnly)) {
        throw std::runtime_error("ApplicationData::createConfigExample(): Output file openning is failed");
    }

    const QString configExample {
        "[GLOBAL]\n"
        "data_path = /srv/ircabot/data\n"
        "# Uncomment \"AJAXIsDisabled\" for disable real time reading mode.\n"
        "# This mode generates many requests from the client side\n"
        "# and in exceptional cases may be undesirable. All JavaScript code will be removed.\n"
        "#AJAXIsDisabled = true\n\n"

        "# Global options for all servers.\n"
        "nick = default_nickname\n"
        "user = default_ident\n"
        "real_name = default_real_name\n"
        "# If password is empty logging in without password.\n"
        "password = password_for_NickServ\n\n"
        "# This triggers available in all chats.\n"
        "# Request and answer must be splitted by \":::\". Triggers splitting by \"<!>\".\n"
        "# You can use \"%CHANNEL_FOR_URL%\" to automatically substitute the address for the chat,\n"
        "# from which the link to the web interface is requested.\n"
        "triggers = version ::: " + global::IRCABOT_VERSION + " <!> webui ::: http://example.com/%CHANNEL_FOR_URL%\n\n"

        "# Web interface options\n"
        "# service_* parameters used for the appearance of the home button\n"
        "service_name = IRCaBot\n"
        "service_emoji = &#128193;\n"
        "bind_to_address = 127.0.0.1\n"
        "bind_to_port = 8080\n\n"

        "[Displayed server name]\n"
        "address = 127.0.0.1\n"
        "port = 6667\n"
        "# Channels splitting with comma.\n"
        "channels = #general,#acetonevideo\n"
        "# This triggers available in current server only.\n"
        "triggers = hi ::: hello <!> developer ::: acetone\n\n"
        "# Optional parameters if global is defined:\n"
        "#nick = unique_nickname_for_this_server\n"
        "#user = unique_ident_for_this_server\n"
        "#real_name = unique_real_name_for_this_server\n"
        "#password = password_for_this_user\n"
    };

    file.write(configExample.toUtf8());
    file.close();
}

ApplicationData::ApplicationData(const QString& pathToConfig) :
    m_webInterfacePort(0),
    m_ajaxIsDisabled(false)
{
    if (not QFile::exists(pathToConfig)) {
        throw std::runtime_error("Configuration file not exist (" + pathToConfig.toStdString() + ")");
    }

    m_file = pathToConfig;
    readConfig();
}

std::pair<QString, quint16> ApplicationData::getWebInterfaceAddress()
{
    if (m_webInterfacePort == 0) {
        throw std::runtime_error("Web interface port is undefined");
    }
    if (m_webInterfaceAddress.isEmpty()) {
        throw std::runtime_error("Web interface address is undefined");
    }

    return {m_webInterfaceAddress, m_webInterfacePort};
}

QList<ConnectionData> ApplicationData::getConnections()
{
    return m_connections;
}

QString ApplicationData::getServiceEmoji()
{
    return m_serviceEmoji;
}

QString ApplicationData::getServiceName()
{
    return m_serviceName;
}

QString ApplicationData::getDataFolder()
{
    return m_dataPath;
}

bool ApplicationData::getAjaxIsDisabled()
{
    return m_ajaxIsDisabled;
}

void ApplicationData::readConfig()
{
    QFile file(m_file);
    if (not file.open(QIODevice::ReadOnly)) {
        throw std::runtime_error("ApplicationData::readConfig(): Can't open file for reading");
    }

    QString line {file.readLine()};
    QString conffile;

    while (not line.isEmpty()) {
        line.remove('\n');
        line.remove('\r');

        if (line.startsWith('#') or line.isEmpty()) {
            line = file.readLine();
            continue;
        }

        conffile += line + '\n';
        line = file.readLine();
    }
    file.close();
    if (conffile.isEmpty()) {
        throw std::runtime_error("ApplicationData::readConfig(): Empty data");
    }

    //// Парсинг GLOBAL
    QString globalSection {conffile};
    int globalBegin = conffile.indexOf("[GLOBAL]");
    if (globalBegin == -1) {
        throw std::runtime_error("ApplicationData::readConfig(): Wrong config. [GLOBAL] section not exist!");
    }
    int globalEnd = conffile.indexOf(QRegularExpression("\\[[^:]*\\]"), globalBegin+1); //IPv6 addresses safe
    if (globalEnd != -1) {
        // Удаление последующей [секции]
        globalSection.remove(globalEnd, conffile.size()-globalEnd);
    }
    globalSection.remove(0, globalBegin);

    //// Инициализация GLOBAL
    // logPath
    m_dataPath = global::getValue(globalSection, "data_path");
    if (m_dataPath.isEmpty()) {
        throw std::runtime_error("ApplicationData::readConfig(): 'data_path' in [GLOBAL] is undefined");
    }
    QDir logDir;
    if (not QFile::exists(m_dataPath)) {
        if (not logDir.mkpath(m_dataPath)) {
            throw std::runtime_error("ApplicationData::readConfig(): Can't create " + m_dataPath.toStdString());
        }
    }
    logDir.setPath(m_dataPath);
    if (not logDir.mkdir("TeSt__FoLdEr")) {
        throw std::runtime_error("ApplicationData::readConfig(): Log path '" + m_dataPath.toStdString() + "' is unwritable");
    }
    logDir.rmdir("TeSt__FoLdEr");

    if (not m_dataPath.endsWith(global::slash)) {
        m_dataPath += global::slash;
    }

    // web interface
    m_webInterfaceAddress = global::getValue(globalSection, "bind_to_address");
    if (m_webInterfaceAddress.isEmpty()) {
        throw std::runtime_error("ApplicationData::readConfig(): 'bind_to_address' in [GLOBAL] is undefined");
    }

    bool success = false;
    m_webInterfacePort = global::getValue(globalSection, "bind_to_port").toUShort(&success);
    if (not success) {
        throw std::runtime_error("ApplicationData::readConfig(): 'bind_to_port' in [GLOBAL] is incorrect");
    }

    m_serviceName = global::getValue(globalSection, "service_name");
    if (m_serviceName.isEmpty()) m_serviceName = "IRCaBot";
    m_serviceEmoji = global::getValue(globalSection, "service_emoji");
    if (m_serviceEmoji.isEmpty()) m_serviceEmoji = "&#128193;";

    QMap<QString, QString> globalTriggers;
    QString triggersLine = global::getValue(globalSection, "triggers");
    if (not triggersLine.isEmpty()) {
        QStringList triggersPair = triggersLine.split("<!>");
        for (auto &p: triggersPair) {
            QStringList pair = p.split(":::");
            if (pair.size() != 2) continue;
            QString request = pair.first();
            QString answer = pair.last();

            if (request.startsWith(' ')) request.remove(QRegularExpression("^\\s*"));
            if (request.endsWith(' ')) request.remove(QRegularExpression("\\s*$"));
            if (answer.startsWith(' ')) answer.remove(QRegularExpression("^\\s*"));
            if (answer.endsWith(' ')) answer.remove(QRegularExpression("\\s*$"));
            if (request.isEmpty() or answer.isEmpty()) continue;

            globalTriggers[request] = answer;
            qInfo().noquote() << "[GLOBAL] Trigger (" << request << ":::" << answer << ")";
        }
    }
    m_ajaxIsDisabled = global::getValue(globalSection, "AJAXIsDisabled") == "true";

    // other
    m_nick = global::getValue(globalSection, "nick");
    m_nick.replace(' ', '_');
    m_user = global::getValue(globalSection, "user");
    m_realName = global::getValue(globalSection, "real_name");
    m_password = global::getValue(globalSection, "password");

    conffile.remove(globalSection);

    //// Парсинг подключений
    // Цикл до тех пор, пока остались заголовки секций
    while (conffile.contains(QRegularExpression("\\[[^\\n]*\\]"))) {
        QString currentSection {conffile};
        int begin = conffile.indexOf(QRegularExpression("\\[[^\\n]*\\]"));
        int end = conffile.indexOf(QRegularExpression("\\[[^:]*\\]"), begin+1);
        if (end != -1) {
            currentSection.remove(end, currentSection.size() - end);
        }
        currentSection.remove(0, begin);
        conffile.remove(currentSection);

        //// Инициализация информации о подключениях
        ConnectionData newConnection;
        newConnection.displayName = currentSection.mid(1, currentSection.indexOf(']')-1);

        newConnection.address = global::getValue(currentSection, "address");
        if (newConnection.address.isEmpty()) {
            qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty 'address')";
            continue;
        }

        success = false;
        newConnection.port = global::getValue(currentSection, "port").toUShort(&success);
        if (not success) {
            qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (wrong 'port')";
            continue;
        }

        QString channelsString = global::getValue(currentSection, "channels");
        if (channelsString.isEmpty()) {
            qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty 'channels')";
            continue;
        }
        newConnection.channels = channelsString.split(',');
        newConnection.channels.removeAll("");
        for (auto &ch: newConnection.channels) {
            ch.remove(' ');
            if (not ch.startsWith('#')) {
                ch = '#' + ch;
            }
        }

        newConnection.user = global::getValue(currentSection, "user");
        if (newConnection.user.isEmpty()) {
            if (m_user.isEmpty()) {
                qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty local 'user' and global too)";
                continue;
            }
            else {
                newConnection.user = m_user;
            }
        }

        newConnection.nick = global::getValue(currentSection, "nick");
        if (newConnection.nick.isEmpty()) {
            if (m_nick.isEmpty()) {
                qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty local 'nick' and global too)";
                continue;
            }
            else {
                newConnection.nick = m_nick;
            }
        }
        else {
            newConnection.nick.replace(' ', '_');
        }

        newConnection.realName = global::getValue(currentSection, "real_name");
        if (newConnection.realName.isEmpty()) {
            if (m_realName.isEmpty()) {
                qInfo().noquote() << "[" + newConnection.displayName + "]" << "ignored (empty local 'real_name' and global too)";
                continue;
            }
            else {
                newConnection.realName = m_realName;
            }
        }

        newConnection.password = global::getValue(currentSection, "password");
        if (newConnection.password.isEmpty() and not m_password.isEmpty()) {
            newConnection.password = m_password;
        }

        QString path {global::toLowerAndNoSpaces(newConnection.displayName)};
        newConnection.logFolderPath = m_dataPath + path;

        QString triggersLine = global::getValue(currentSection, "triggers");
        if (not triggersLine.isEmpty()) {
            QStringList triggersPair = triggersLine.split("<!>");
            for (auto &p: triggersPair) {
                QStringList pair = p.split(":::");
                if (pair.size() != 2) continue;
                QString request = pair.first();
                QString answer = pair.last();

                if (request.startsWith(' ')) request.remove(QRegularExpression("^\\s*"));
                if (request.endsWith(' ')) request.remove(QRegularExpression("\\s*$"));
                if (answer.startsWith(' ')) answer.remove(QRegularExpression("^\\s*"));
                if (answer.endsWith(' ')) answer.remove(QRegularExpression("\\s*$"));
                if (request.isEmpty() or answer.isEmpty()) continue;

                newConnection.triggers[request] = answer;
                qInfo().noquote() << "["+newConnection.displayName+"] Trigger (" << request << ":::" << answer << ")";
            }
        }
        for (auto &glob: globalTriggers) {
            bool detected = false;
            for (auto local: newConnection.triggers) {
                if (newConnection.triggers.key(local) == globalTriggers.key(glob)) {
                    qInfo().noquote() << "[" + newConnection.displayName + "]" <<
                                         "Trigger '" + globalTriggers.key(glob) + "' ignored (GLOBAL)";
                    detected = true;
                }
            }
            if (not detected) {
                newConnection.triggers[globalTriggers.key(glob)] = glob;
            }
        }

        m_connections.push_back(newConnection);
    }
}
